/*
 * DG (DG Gaming Engine)  - API
 * Copyright (C) 2004  Bob Marks (marksie531@yahoo.com)
 * http://code.google.com/p/dng-gaming
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package com.dg.gaming.client;

import java.net.Socket;

import nanoxml.XMLElement;
import com.dg.gaming.api.common.*;
import com.dg.gaming.api.common.msg.MsgGameMessage;
import com.dg.gaming.api.common.msg.MsgTableMessage;
import java.io.*;
import nanoxml.*;
import java.net.*;
import com.dg.gaming.api.common.msg.*;

/**
 * <p>Client connection which is spawned from the client machine and receives/
 * sends communication to/from the server.  This class also holds a mirrored
 * copy of the TableList and UserList objects which are stored on the
 * DGServer.</p>
 *
 * <p>The communication between this connection thread and the GUI (e.g.
 * an implementation of the DGClientFrame) must goes through
 * an interface IClient to ensure maximum abstraction.</p>
 *
 * @author  Bob Marks
 * @version Beta 0.3
 */
public class ClientConnectionThread extends AbstractConnectionThread implements
        Runnable {

    /** Interface betweem this thread and the Frame */
    protected IClient clientInterface;

    /** Current game ID. */
    protected String gameID = null;

    /** Game associated with this connection thread (will be
     *  mirrored to that on the server. */
    protected Game game = null;

    /** Link to data communication receiver. */
    private ClientCommDataReceiver commDataReceiver = null;
    /** Socket between the server and the user. */
    protected Socket socket;

    /** Buffered input. */
    protected BufferedReader in;

    /** PrintStream for the output. */
    private PrintStream out;
    /**
     * Default constructor which takes a Socket connection to the server, a
     * username and an IClient which sits between this class and the GUI.
     *
     * @param connection       Socket connection to server.
     * @param username         Username
     * @param clientInterface  Interface between this class and GUI.
     */
    public ClientConnectionThread(Socket connection, long userid,
                                  IClient clientInterface) {
        super();
        try {
            logger.debug("AbstractConnectionThread",
                         "Creating new in/out streams.");

            setSocket(connection);
        } catch (IOException ioEx) {
            logger.error("AbstractConnectionThread", "IO Exception.");
            logger.stacktrace(ioEx);
        }
        this.userid = userid;
        this.clientInterface = clientInterface;
        this.game = null;
    }

    /**
     * @param socket
     * @throws IOException
     */
    protected void setSocket(Socket socket) throws IOException {
        this.socket = socket;

        if (socket != null) {
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintStream(socket.getOutputStream());
        }
    }

    /**
     * Set the client interface.
     *
     * @param clientInterface  Client interface
     */
    public void setClientInterface(IClient clientInterface) {
        this.clientInterface = clientInterface;
    }

    /**
     * Parse method which reads the first token of the message and delegate
     * to the implementating client.
     *
     * @see com.dg.gaming.api.common.AbstractConnectionThread#parse(nanoxml.XMLElement)
     */
    public void parse(XMLElement message) {
        // Update data first of all
        String sTableNum = message.getStringAttribute(MsgTableMessage.
                XML_ATT_TABLE_NUM);

        // If message contains a "table" attribute then delegate to a table message
        if (sTableNum == null) {

            // Update data tree first
            if (commDataReceiver != null) {
                commDataReceiver.receiveGameMessage(message, userid);
            }

            // Delegate to client interface for further processing
            this.clientInterface.receiveGameMessage(message);
        } else {
            String tableNum = sTableNum;

            // Update data tree first
            if (commDataReceiver != null) {
                commDataReceiver.receiveTableMessage(message, userid,
                        tableNum);
            }

            this.clientInterface.receiveTableMessage(message, tableNum);
        }
    }

    /**
     * Send a ITransmittable object from a client to the server.
     *
     * @param message
     */
    public void send(MsgGameMessage message) {
        this.send((ITransmittable) message);
    }

    /**
     * Run method - runs until an exception has occured or the loop variable
     * becomes false.
     *
     * @see java.lang.Runnable#run()
     */
    public void run() {
        logger.debug("run", "Staring thread.");

        try {
            while (loop) {
                // listen for input from the user
                String inString = "";

                while (loop && inString != null && inString.equals("")) {
                    if (in == null) {
                        cleanup();
                        return;
                    }
                    inString = in.readLine();
//
//                    int aval = in.available();
//                    if (aval > 0) {
//                        byte[] bt = new byte[aval];
//                        in.read(bt);
//                        inString = new String(bt, "UTF8");
//                    } else {
//                        inString = "";
//                    }
                }

                // Check input.
                if (in == null) {
                    cleanup();
                    return;
                }

                // Ensure that the communication is XML (starts with a '<' character)
                // as any sort of client could send communication to the server.
                if (inString != null) {
                    if (inString.startsWith("<")) {

                        // Starts with an '<' so try and parse this XML
                        XMLElement message = new XMLElement();

                        try {
                            message.parseString(inString);

                            // parse this element
                            if (message != null) {
                                parse(message);
                            }
                        } catch (XMLParseException xmlParseEx) {
                            logger.error("run", "problem parsing: " + inString);
                            logger.stacktrace(xmlParseEx);
                        }
                    }
                }
            }
        } catch (SocketException sEx) {
            logger.debug("run", "Connection lost");
            logger.stacktrace(sEx);
        } catch (IOException ioEx) {
            logger.error("run", "IO Exception: ");
            logger.stacktrace(ioEx);
        } catch (Exception genEx) {
            genEx.printStackTrace();
            logger.error("run", "General Exception: ");
            logger.stacktrace(genEx);
        }

        connected = false;
        cleanup();
    }

    /**
     * Return the user list (should be the same as the server user
     * list object).
     *
     * @return   List of users.
     */
    public UserList getUserList() {
        return this.game.getUserList();
    }

    /**
     * Update the user list.
     *
     * @param userList   List of users
     */
    public void setUserList(UserList userList) {
        this.game.setUserList(userList);
    }

    /**
     * Return the gameID.
     *
     * @return  Game ID.
     */
    public String getGameID() {
        return this.gameID;
    }

    /**
     * Return the game object which should be mirrored to that
     * on the server.
     *
     * @return       Game object consisting of user and table lists.
     */
    public Game getGame() {
        return this.game;
    }

    /**
     * Set the game object from the server.
     *
     * @param game
     */
    public void setGame(Game game) {
        this.game = game;

        this.commDataReceiver = new ClientCommDataReceiver(game);
    }

    /**
     * Return the table list object (should be the same as the server table
     * list).
     *
     * @return  TableList object (contains number of Table objects).
     */
    public TableList getTableList() {
        return this.game.getTableList();
    }

    /**
     * Update the table list.
     *
     * @param tableList   TableList object containing Table objects.
     */
    public void setTableList(TableList tableList) {
        this.game.setTableList(tableList);
    }

    /**
     * Stop the thread.
     */
    protected void disconnect() {
        super.loop = false; // End thread
    }

    /**
     * Client has exitted so clean everything up.
     *
     * @see com.dg.gaming.api.common.AbstractConnectionThread#cleanup()
     */
    public void cleanup() {
        // Clean up
    }

    /**
     * Send a ITransmittable object to the output stream (could be server or
     * client).
     *
     * @param transObject
     */
    public void send(ITransmittable transObject) {
        // Retrieve XMLElement from the object and flatten to a String.
        String message = transObject.flatten().toString();

        // Send down the socket to the receiving end
        try {
            System.out.println(message);
            out.write(message.getBytes("UTF8"));
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException ex1) {
            }
        } catch (IOException ex) {
        }
    }

}
